#!/usr/bin/env python

import re
from event import *

DEBUG = False
#DEBUG = True

class PolishLanguage(Exception):
  pass

class ParseError(Exception):
  pass

#--------------------------------------------------------------------#
# Data 
#--------------------------------------------------------------------#

class PlayerData(object):
  def __init__(self):
    pass

class TeamData(object):
  def __init__(self):
    pass
    #self.players = dict((i, PlayerData()) for i in range(1, 17))

class LogData(object):
  def __init__(self, start_event):
    self.turns = [[] for _ in range(50)]

    # eventually this should be replaced by a mapping from code to Team entity
    team_code_map = start_event.team_code_map
    self.code_map = team_code_map

    player_code_map = {} # TODO
    self.code_map.update(player_code_map)

  def log_event(self, turn, event):
    self.turns[turn].append(event)

  def dump(self, detail=True):
    for i, turn in enumerate(self.turns):
      print
      print "TURN %s" % i
      print "-------"
      for event in turn:
        text = event.get_string_template(detail)
        if text:
          print text.safe_substitute(self.code_map)

  def get_action_sequences(self):
    action_sequences = {}

    for i, turn in enumerate(self.turns):
      active_player = None
      action_sequence = []

      for event in turn:
        if isinstance(event, ActionEvent):
          if active_player != event.get_code():
            if action_sequence:
              action_sequences.setdefault(
                  active_player, []).append(action_sequence)

            action_sequence = []
            active_player = event.get_code()

          action_sequence.append(event)

    return action_sequences

  def _flush_action_sequence(self):
    self.active_player_code = None
    if len(self.action_sequence):
      self.log_data.action_sequences.append(self.action_sequence)
      self.action_sequence = []


# Helpers 
#--------------------------------------------------------------------#

def dprint(text):
  if DEBUG:
    print text

# Parser class
#--------------------------------------------------------------------#

class _Parser(object):
  def __init__(self, text):
    self.text = text
    self.log_data = None
    self.event_regex = re.compile("GameLog\((?P<code>..)\):\s?(?P<desc>.*$)")
    self.event_parsers = {
        '-1': self._parse_turnover_event,
        '00': self._parse_blitz_event,
        '02': self._parse_action_event,
        '03': self._parse_kickoff_event,
        '04': self._parse_halftime_event,
        '05': self._parse_count_event,
        '06': self._parse_match_event,
        '07': self._parse_touchdown_event,
        '08': self._parse_reroll_event,
        '11': self._parse_skill_event,
        '12': self._parse_spp_event,
        '13': self._parse_apoth_event,
        }

  def parse(self):
    lines = self.text.splitlines()
    lines.append("GameLog(06): Game Over")

    for line in lines:
      code, desc = self.event_regex.match(line).groups()
      event = self.event_parsers[code](desc)

      dprint(desc)
      if not isinstance(event, IgnoredEvent):
        self._prehandle_event(event)
        self._log_event(event)
        self._posthandle_event(event)

    return self.log_data

  def _prehandle_event(self, event):

    # Check for match start.  Even though there may be multiple matches in
    # this log, the last one is necessarily the one we want so we just clear
    # everything.
    if isinstance(event, MatchStartEvent):

      # for tracking turn information
      self.active_team_code = None
      self.turn = 0

      # for tracking event chains
      self.chain_event = None

      # log information
      self.log_data = LogData(event)

    if isinstance(event, ActionEvent):

      # Check whether we need to advance the turn information
      if event.team_code != self.active_team_code:
        self._advance_turn()
      self.active_team_code = event.team_code

  def _log_event(self, event):

    if self.chain_event:
      # What is log_event below?  There is only one case where it is used, and
      # that is for back-tracking when we don't know a double-armor check is
      # because of piling on (speculated) or a subsequent foul.
      # In that case the log_event returned is the misspeculated armor break.
      result = self.chain_event.chain(event)

      if isinstance(result, tuple) and len(result) == 2:
        dprint("-- bonus event")
        self.log_data.log_event(self.turn, result[1])
        result = result[0]

      if result == ChainEvent.CHAINED:
        dprint("-- chained")
        return
      elif result == ChainEvent.END:
        dprint("-- chain ended")
        self.chain_event = None
      else:
        assert result == ChainEvent.SKIP, result

    if isinstance(event, ChainEvent):
      dprint("-- new chain event")
      self.chain_event = event

    self.log_data.log_event(self.turn, event)

  def _posthandle_event(self, event):

    # half time and end match advance the turn counter
    for EventClass in (
        HalfTimeEvent,
        MatchEndEvent):
      if isinstance(event, EventClass):
        self._advance_turn()

  def _advance_turn(self):
    dprint("-- _advance_turn")
    self.chain_event = None
    self.turn += 1

  # event parsers
  #--------------------------------------------------------------------#

  def _parse_turnover_event(self, desc):
    for EventClass in (
        NewMatchEvent,
        TurnoverEvent):
      if EventClass.check(desc):
        return EventClass(desc)

    raise NotImplementedError(desc)

  def _parse_blitz_event(self, desc):
    if BlitzEvent.check(desc):
      return BlitzEvent(desc)

    raise NotImplementedError(desc)

  def _parse_action_event(self, desc):
    for EventClass in (
        AlwaysHungryEvent,
        ArmorCheckEvent,
        BlockEvent,
        BlockChoiceEvent,
        BloodLustEvent,
        BombEvent,
        BoneHeadEvent,
        BounceEvent,
        CasualtyCheckEvent,
        CatchEvent,
        ChainsawEvent,
        DauntlessEvent,
        DodgeEvent,
        EjectionEvent,
        FireballEvent,
        FoulAppearanceEvent,
        GFIEvent,
        HypnoticGazeEvent,
        InjuryCheckEvent,
        JumpUpEvent,
        KORecoverEvent,
        KickDistanceEvent,
        KickEvent,
        LandEvent,
        LeapEvent,
        LightningEvent,
        LonerEvent,
        PickUpEvent,
        ReallyStupidEvent,
        RegenEvent,
        SafeThrowEvent,
        ScatterEvent,
        ShadowingEvent,
        StandUpEvent,
        SwelteringHeatEvent,
        TakeRootEvent,
        TentaclesEvent,
        ThrowEvent,
        ThrowTeamMateEvent,
        ThrowInDirectionEvent,
        ThrowInDistanceEvent,
        WildAnimalEvent,
        ):
      if EventClass.check(desc):
        return EventClass(desc)

    raise NotImplementedError(desc)

  def _parse_kickoff_event(self, desc):
    if KickOffEvent.check(desc):
      return KickOffEvent(desc)

    raise NotImplementedError(desc)

  def _parse_halftime_event(self, desc):
    if HalfTimeEvent.check(desc):
      return HalfTimeEvent(desc)

    raise NotImplementedError(desc)

  def _parse_count_event(self, desc):
    if PlayerCountEvent.check(desc):
      return PlayerCountEvent(desc)

    raise NotImplementedError(desc)

  def _parse_match_event(self, desc):
    if desc.startswith("Wyb"):
      raise PolishLanguage(desc)

    for EventClass in (
        CoinOutcomeEvent,
        CoinTossEvent,
        MatchEndEvent,
        MatchStartEvent,
        WeatherEvent):
      if EventClass.check(desc):
        return EventClass(desc)

    raise ParseError(desc)

  def _parse_touchdown_event(self, desc):
    if TouchdownEvent.check(desc):
      return TouchdownEvent(desc)

    raise NotImplementedError(desc)

  def _parse_reroll_event(self, desc):
    for EventClass in (
        BribeEvent,
        RerollEvent):
      if EventClass.check(desc):
        return EventClass(desc)

    raise NotImplementedError(desc)

  def _parse_skill_event(self, desc):
    if SkillEvent.check(desc):
      return SkillEvent(desc)

    raise NotImplementedError(desc)

  def _parse_spp_event(self, desc):
    if SPPEvent.check(desc):
      return SPPEvent(desc)

    raise NotImplementedError(desc)

  def _parse_apoth_event(self, desc):
    for EventClass in (
        ApothHealEvent,
        ApothEvent):
      if EventClass.check(desc):
        return EventClass(desc)

    raise NotImplementedError(desc)

#--------------------------------------------------------------------#
# API
#--------------------------------------------------------------------#

def parse(text):
  try:
    return _Parser(text).parse()
  except ParseError, e:
    print e
    return None
  except PolishLanguage, e:
    print e
    return None

if __name__ == '__main__':

  def test(i):
    if i == 0:
      log_data = parse(open('sample.txt').read())
    else:
      log_data = parse(open('test_data/%s.txt' % i).read())

  try:
    for i in range(100, 500):
      test(i)

  except:
    DEBUG = True
    try:
      test(i)
    except:
      print i
      raise

    #log_data.dump()

    #action_sequences = log_data.get_action_sequences()
    #for player_code, sequences in action_sequences.items():
    #  print player_code
    #  for seq in sequences:
    #    print "  ", seq



